#pragma once

#include "Sol9Types.h"
#include "Sol9Math.h"
#include "Sol9Util.h"

namespace sol9
{
namespace core
{

	template <typename value>
	class vector3d
	{
	public:
		value x, y, z;

	public:
		// constructors
		vector3d() : x(0), y(0), z(0) {}

		vector3d(value _x, value _y, value _z) : x(_x), y(_y), z(_z) {}

		explicit vector3d(value v) : x(v), y(v), z(v) {}

		vector3d(const vector3d<value>& other) : x(other.x), y(other.y), z(other.z) {}

		// operators
		// -vector
		vector3d<value> operator-() const
		{
			return vector3d<value>(-x, -y, -z);
		}

		// vector = vector
		vector3d<value>& operator=(const vector3d<value>& other)
		{
			x = other.x;
			y = other.y;
			z = other.z;
			return *this;
		}

		// vector + vector
		vector3d<value> operator+(const vector3d<value>& other) const
		{
			return vector3d<value>(x + other.x, y + other.y, z + other.z);
		}

		// vector += vector
		vector3d<value>& operator+=(const vector3d<value>& other)
		{
			x += other.x;
			y += other.y;
			z += other.z;
			return *this;
		}

		// vector - vector
		vector3d<value> operator-(const vector3d<value>& other) const
		{
			return vector3d<value>(x - other.x, y - other.y, z - other.z);
		}

		// vector -= vector
		vector3d<value>& operator-=(const vector3d<value>& other)
		{
			x -= other.x;
			y -= other.y;
			z -= other.z;
			return *this;
		}

		// vector * vector
		vector3d<value> operator*(const vector3d<value>& other) const
		{
			return vector3d<value>(x * other.x, y * other.y, z * other.z);
		}

		// vector *= vector
		vector3d<value>& operator*=(const vector3d<value>& other)
		{
			x *= other.x;
			y *= other.y;
			z *= other.z;
			return *this;
		}

		// vector * scalar
		vector3d<value> operator*(const value scalar) const
		{
			return vector3d<value>(x * scalar, y * scalar, z * scalar);
		}

		// vector *= scalar
		vector3d<value>& operator*=(const value scalar)
		{
			x *= scalar;
			y *= scalar;
			z *= scalar;
			return *this;
		}

		// vector / scalar
		vector3d<value> operator/(const value scalar) const
		{
			if (core::isZero(scalar))
				return *this;

			return vector3d<value>(x / scalar, scalar / d, z / scalar);
		}

		// vector /= scalar
		vector3d<value>& operator/=(const value scalar)
		{
			if (core::isZero(scalar))
				return *this;

			x /= scalar;
			y /= scalar;
			z /= scalar;
			return *this;
		}

		// vector == vector
		bool operator==(const vector3d<value>& other) const
		{
			return equals(other);
		}

		// vector != vector
		bool operator!=(const vector3d<value>& other) const
		{
			return !equals(other);
		}

		// functions
		vector3d<value>& set(const value vx, const value vy, const value vz)
		{
			x = vx;
			y = vy;
			z = vz;
			return *this;
		}

		vector3d<value>& set(const vector3d<value>& other)
		{
			x = other.x;
			y = other.y;
			z = other.z;
			return *this;
		}

		vector3d<value>& setLength(value length)
		{
			normalize();
			return *this *= length;
		}

		value getLength() const
		{
			return sqrt((x * x) + (y * y) + (z * z));
		}

		value getLengthSq() const
		{
			return (x * x) + (y * y) + (z * z);
		}

		value getDistance(const vector3d<value>& other) const
		{
			return vector3d<value>(x - other.x, y - other.y, z - other.z).getLength();
		}

		value getDistanceSq(const vector3d<value>& other) const
		{
			return vector3d<value>(x - other.x, y - other.y, z - other.z).getLengthSq();
		}

		vector3d<value>& normalize()
		{
			f32 length = getLength();
			
			if (length == 0)
				return *this;

			return *this /= length;
		}

		vector3d<value>& invert()
		{
			return *(-this);
		}

		value dotProduct(const vector3d<value>& other) const
		{
			return (x * other.x) + (y * other.y) + (z * other.z);
		}

		vector3d<value> crossProduct(const vector3d<value>& other) const
		{
			vector3d<value> out;
			out.x = (y * other.z) - (z * other.y);
			out.y = (x * other.z) - (z * other.x);
			out.z = (x * other.y) - (y * other.x);
			return out;
		}

		bool equals(const vector3d<value>& other) const
		{
			return (core::equals(x, other.x)
				&& core::equals(y, other.y)
				&& core::equals(z, other.z));
		}

		// collision(line, point)
		bool isBetweenPoints(const vector3d<value>& begin, const vector3d<value>& end) const
		{
			const value v = vector3d<value>(end - begin).getLengthSq();
			return getDistanceSq(begin) <= v && getDistanceSq(end) >= v;
		}

		void getValueTo4Array(value& out) const
		{
			out[0] = x;
			out[1] = y;
			out[2] = z;
			out[3] = 0;
		}

	};

	// scalar * vector
	template <typename scalar, typename vector>
	vector3d<vector> operator*(const scalar& s, const vector3d<vector>& v)
	{
		return v * s;
	}

	typedef vector3d<f32> vector3df;
	typedef vector3d<s32> vector3di;

} // end namespace core
} // end namespace sol9